# -*- coding: utf-8 -*-
#
# Copyright 2009 Vanderbilt University
# 
# Licensed under the Apache License, Version 2.0 (the "License"); 
# you may not use this file except in compliance with the License. 
# You may obtain a copy of the License at 
# 
#     http://www.apache.org/licenses/LICENSE-2.0 
# 
# Unless required by applicable law or agreed to in writing, software 
# distributed under the License is distributed on an "AS IS" BASIS, 
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
# See the License for the specific language governing permissions and 
# limitations under the License. 

"""Primary objects for running and configuring HORNET. 

.. moduleauthor:: John Paulett <john.paulett -at- vanderbilt.edu>
"""
from hornet.event import ProcessingStart
from hornet.util import make_periods
import logging

logger = logging.getLogger(__name__)

def root_plugins(plugins):
    """For the list of *plugins*, find which ones are not listening to 
    any other plugins.  These plugins are consider the root plugins and
    will be started with a :class:`hornet.event.ProcessingStart` event.
    """
    # TODO consider also filtering by the plugins that accept ProcessingStart
    return [p for p in plugins if len(p.listen_to) == 0]
        
def setup_plugins(plugins):
    """Call the setup() method on all *plugins*."""
    for plugin in plugins:
        plugin.setup()

def bind_plugins(parent, plugins_dict):
    """Associate the *parent* with all the plugins and set the 
    :attr:`hornet.plugin.Plugin.id` attributes of the plugins to the keys
    of *plugins_dict*. Returns a list of the plugins.
    """
    for pid, plugin in plugins_dict.iteritems():
        # persist the id as the name of the plugin
        plugin._id = pid
        plugin._parent = parent
    return plugins_dict.values()
    
class InvalidConfiguration(Exception):
    """The configuration provided contains an invalid option."""
    
class Config(object):
    """Configuration object defines the preferences for running HORNET, 
    including all the plugins that should be run.
    
    **Required Attributes**:
    
        * *plugins* (dict(str, :class:`hornet.plugin.Plugin`)) - Dictionary 
          plugins to execute. The dictionary keys should be unique identifiers 
          for each plugin instance.
        * *output_dir* (string) - Path to save output.
        * *start* (:class:`~datetime.date`) - Start date of period to analyze. 
        * *end* (:class:`~datetime.date`) - End date of period to analyze  
        * *period_size* (:class:`~datetime.timedelta`) - Size of time to split 
          the period by. 
        
    """
    def __new__(cls):
        obj = object.__new__(cls)
        
        ## create the attributes 
        obj.plugins = {}
        obj.output_dir = None
        obj.start = None
        obj.end = None
        obj.period_size = None
        
        return obj

class PluginRunner(object):
    """Main application loop for HORNET. Takes a configuration file and runs
    the plugins as specified by that config file."""
    def __init__(self, config):
        self.config = config
        # Convert the config'ed plugins into a list and store the name
        self.plugins = bind_plugins(self, config.plugins)
        
        self.periods = make_periods(config.start, config.end, 
                                    config.period_size)
    
    def run(self):
        """Begin execution of the plugins."""
        logger.debug('Setting up plugins')
        setup_plugins(self.plugins)

        logger.debug('Running plugins')
        
        roots = root_plugins(self.plugins)
        if not roots:
            raise InvalidConfiguration, \
                 'No plugins are able to start processing.'

        event = ProcessingStart(data=self.periods)
        for plugin in roots:
            plugin.notify(event) 
        
        logger.debug('Finished primary computation loop')
        
    def stop(self):
        """Teardown the plugins."""
        for plugin in self.plugins:
            plugin.teardown()
        del self.plugins

    def get_plugin(self, plugin_id):
        """Returns the instance of the plugin specified by the id of 
        *plugin_id*. The *plugin_id* is the :
        """
        for plugin in self.plugins:
            if plugin_id == plugin.id:
                return plugin
        raise KeyError, 'No such plugin registered: %s' % plugin_id
        
    